﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Futaba.SBus.Channels
{
	/// <summary>
	/// The default implementation of the <see cref="IChannelParser"/>
	/// </summary>
	public class ChannelParser : NotifyPropertyChangedBase, IChannelParser
	{

		#region Singleton

		private static IChannelParser _singletonChannelParser;
		private static readonly object ChannelParserSingletonLock = new object();

		/// <summary>
		/// A singleton of the default implementation of <see cref="IChannelParser"/>
		/// </summary>
		public static IChannelParser DefaultInstance
		{
			get
			{
				if (_singletonChannelParser == null)
				{
					// ReSharper disable PossibleMultipleWriteAccessInDoubleCheckLocking
					lock (ChannelParserSingletonLock)
					{
						if (_singletonChannelParser == null)
							_singletonChannelParser = new ChannelParser();
					}
					// ReSharper restore PossibleMultipleWriteAccessInDoubleCheckLocking
				}
				return _singletonChannelParser;
			}
		}

		#endregion

		/// <summary>
		/// The PWM value for stick-center
		/// </summary>
		public static int StickCenterValue = 1024;
		/// <summary>
		/// The PWM step size for each stick-percent
		/// </summary>
		public static double StickPercentStep = 6.71;

		const byte _startByte = (byte)0x0f;

		#region ISbusParser
		private Readers.ISbusStreamReader _internalReader;
		/// <summary>
		/// The reader which reads from the actual stream
		/// </summary>
		public Readers.ISbusStreamReader Reader
		{
			get { return _internalReader; }
			set
			{
				if (Equals(_internalReader, value))
					return;
				_internalReader = value;
				RaisePropertyChanged(); //RaisePropertyChanged("Reader");
			}
		}

		private int _internalBytesSkipped;
		/// <summary>
		/// Returns the amount of bytes which were not considered part of a valid package
		/// </summary>
		/// <seealso cref="Reset"/>
		public int BytesSkipped
		{
			get { return _internalBytesSkipped; }
			protected set
			{
				if (Equals(_internalBytesSkipped, value))
					return;
				_internalBytesSkipped = value;
				RaisePropertyChanged(); //RaisePropertyChanged("BytesSkipped");
			}
		}

		private int _internalPacketsReceived;
		/// <summary>
		/// Returns the amount of packets which were successfully received and processed
		/// </summary>
		/// <seealso cref="Reset"/>
		public int PacketsReceived
		{
			get { return _internalPacketsReceived; }
			protected set
			{
				if (Equals(_internalPacketsReceived, value))
					return;
				_internalPacketsReceived = value;
				RaisePropertyChanged(); //RaisePropertyChanged("PacketsReceived");
			}
		}

		private int _internalPacketErrors;
		/// <summary>
		/// Returns the amount of packets which were not fully processed
		/// </summary>
		/// <seealso cref="Reset"/>
		public int PacketErrors
		{
			get { return _internalPacketErrors; }
			protected set
			{
				if (Equals(_internalPacketErrors, value))
					return;
				_internalPacketErrors = value;
				RaisePropertyChanged(); //RaisePropertyChanged("PacketErrors");
			}
		}

		private IList<Channel> _internalItemsCollection;
		/// <summary>
		/// Holds the data of the parser
		/// </summary>
		public IList<Channel> ItemsCollection
		{
			get { return _internalItemsCollection; }
			set
			{
				if (Equals(_internalItemsCollection, value))
					return;
				_internalItemsCollection = value;
				RaisePropertyChanged(); //RaisePropertyChanged("ItemsCollection");
			}
		}

		private bool _internalCancellationPending;
		/// <summary>
		/// Set to True from <see cref="ISbusParsingService"/>, if the current operation is to be aborted
		/// </summary>
		public bool CancellationPending
		{
			get { return _internalCancellationPending; }
			set
			{
				if (Equals(_internalCancellationPending, value))
					return;
				_internalCancellationPending = value;
				RaisePropertyChanged(); //RaisePropertyChanged("CancellationPending");
			}
		}

		/// <summary>
		/// Raised when an element in the <see cref="ItemsCollection"/> has changed
		/// </summary>
		public event EventHandler<Channel> ItemUpdated;

		private void RaiseItemUpdated(Channel channel)
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine("Raising ItemUpdated", GetType().Name);
#endif
			if (ItemUpdated == null)
				return;

			ItemUpdated.BeginInvoke(this, channel, r => HandleEventInvocation(r, ItemUpdated.EndInvoke), ItemUpdated);
		}

		/// <summary>
		/// Raised (once per data-packet) when at least one element in the <see cref="ItemsCollection"/> has changed
		/// </summary>
		public event EventHandler ItemsUpdated;

		private void RaiseItemsUpdated()
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine("Raising ItemsUpdated", GetType().Name);
#endif
			if (ItemsUpdated == null)
				return;

			ItemsUpdated.BeginInvoke(this, EventArgs.Empty, r => HandleEventInvocation(r, ItemsUpdated.EndInvoke), ItemsUpdated);
		}

		/// <summary>
		/// Raised when a valid S.BUS packet has been received and parsed
		/// (regardless wether any channel has changed)
		/// </summary>
		public event EventHandler DataReceived;

		private void RaiseDataReceived()
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine("Raising DataReceived", GetType().Name);
#endif
			if (DataReceived == null)
				return;

			DataReceived.BeginInvoke(this, EventArgs.Empty, r => HandleEventInvocation(r, DataReceived.EndInvoke), DataReceived);
		}


		#region bus-data detection

		/// <summary>
		/// Detect, if this type of data is being supplied in the current stream
		/// </summary>
		/// <returns>True, if this instance can parse data from the current <see cref="Reader"/></returns>
		public bool DetectData(byte[] data)
		{
			var firstIdx = Array.IndexOf(data, _startByte);
			if (firstIdx < 0)
				return false;

			while (true)
			{
				var secondIdx = Array.IndexOf(data, _startByte, firstIdx + 1);
				if (secondIdx < 0)
					return false;

				var dif = (secondIdx - firstIdx);
				switch (dif)
				{
					case 24: //only channel-data
#if DEBUG
						System.Diagnostics.Debug.WriteLine("Detected S.BUS only", "ChannelParser");
#endif
						//ChannelDataAvailable = true;
						//TelemetryDataAvailable = false;
						break;
					case 48: //channel and telemetry data with Slot8 bug
					case 49: //channel and telemetry data with Slot8 bug
					case 50: //channel and telemetry data
#if DEBUG
						System.Diagnostics.Debug.WriteLine("Detected S.BUS+S.BUS2 data", "ChannelParser");
#endif
						//ChannelDataAvailable = true;
						//TelemetryDataAvailable = true;
						break;
					default: //0x0f seems to be payload
						firstIdx = secondIdx;
						break;
				}
				SkipToChannelMessageStart();
				return true;
			}
		}

		/// <summary>
		/// Reads while the channel-start has not been reached
		/// </summary>
		/// <returns>returns the amount of bytes skipped</returns>
		protected int SkipToChannelMessageStart()
		{
			int bytesSkipped = 0;
			while (true)
			{
				var value = Reader.Peek();
				if (!value.HasValue)
					continue;
				if (_startByte == value.Value)
					return bytesSkipped;
				Reader.ReadByte();
				bytesSkipped++;
			}
		}
		#endregion

		/// <summary>
		/// Read and parse data from the stream
		/// </summary>
		/// <returns>True, if a valid packet was detected, else False</returns>
		public bool ReadPacket()
		{
			if (ItemsCollection == null || ItemsCollection.Count == 0)
				ItemsCollection = ChannelFactory.DefaultInstance.GetDefaultChannels();

			var success = TryParseChannels();
			if (success)
				PacketsReceived++;
			else if (!CancellationPending)
				PacketErrors++;
			return success;
		}

		/// <summary>
		/// Reset the counters
		/// </summary>
		/// <seealso cref="BytesSkipped"/>
		/// <seealso cref="PacketsReceived"/>
		/// <seealso cref="PacketErrors"/>
		public void Reset()
		{
			if (ItemsCollection != null)
			{
				foreach (var ch in ItemsCollection)
					ch.CurrentValue = null;
			}

			CancellationPending = false;
			BytesSkipped = PacketsReceived = PacketErrors = 0;
			ReceiverState = ReceiverStatusType.Unknown;
		}

		#endregion

		#region IChannelParser

		private ReceiverStatusType _internalReceiverState;
		/// <summary>
		/// The receiver's current state
		/// </summary>
		/// <remarks>
		/// This state information is usually only available when reading the data-stream on the model's RX
		/// </remarks>
		public ReceiverStatusType ReceiverState
		{
			get { return _internalReceiverState; }
			set
			{
				if (Equals(_internalReceiverState, value))
					return;
				_internalReceiverState = value;
				RaisePropertyChanged(); //RaisePropertyChanged("ReceiverState");
				RaiseReceiverStateChanged();
			}
		}

		/// <summary>
		/// Raised when the receiver's <see cref="ReceiverState">state</see> has changed
		/// </summary>
		/// <seealso cref="ReceiverState"/>
		public event EventHandler ReceiverStateChanged;

		private void RaiseReceiverStateChanged()
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine("Raising ReceiverStateChanged", GetType().Name);
#endif
			if (ReceiverStateChanged == null)
				return;

			ReceiverStateChanged.BeginInvoke(this, EventArgs.Empty, r => HandleEventInvocation(r, ReceiverStateChanged.EndInvoke), ReceiverStateChanged);
		}

		#endregion //IChannelParser

		#region ChannelParsing

		/// <summary>
		/// Tries to read and parse the channel-packet
		/// </summary>
		/// <returns></returns>
		protected bool TryParseChannels()
		{
			var data = TryReadSbusRecord();
			if (data == null)
				return false;

			if (CancellationPending)
				return false;

			var channels = ParseChannels(data);
			RaiseDataReceived();

			bool anyChanged = AssignValues(channels);
			if (anyChanged)
				RaiseItemsUpdated();

			return true;
		}

		/// <summary>
		/// Reads the channel-packet from the reader
		/// </summary>
		/// <returns></returns>
		protected internal byte[] TryReadSbusRecord()
		{
			var inBuffer = new byte[24];
			int bufferIndex = 0;

			var bytesSkipped = SkipToChannelMessageStart();
			BytesSkipped += bytesSkipped;

			if (CancellationPending)
				return null;

			while (!CancellationPending)
			{
				var inData = Reader.ReadByte();
				if (!inData.HasValue)
					continue;

				inBuffer[bufferIndex++] = inData.Value;
				if (bufferIndex == 24)
				{
					if (inBuffer[0] == 0x0f)
						return inBuffer;
					return null;
				}
			}
			return null;
		}

		/// <summary>
		/// Parses the channel-packet and returns the raw channel-values, already converted from PWM 
		/// values to percentage values (-155% to 155%)
		/// </summary>
		/// <param name="data">the data to parse</param>
		protected internal int[] ParseChannels(byte[] data)
		{
			//16 prop and 2 digi channels
			var channels = new int[18];

			// reset counters
			int byteInSbus = 1;
			int bitInSbus = 0;
			byte ch = 0;
			byte bitInChannel = 0;

			// process actual sbus data
			for (int i = 0; i < 176; i++)
			{
				if ((data[byteInSbus] & (1 << bitInSbus)) != 0)
				{
					channels[ch] |= (1 << bitInChannel);
				}
				bitInSbus++;
				bitInChannel++;

				if (bitInSbus == 8)
				{
					bitInSbus = 0;
					byteInSbus++;
				}
				if (bitInChannel == 11)
				{
					bitInChannel = 0;
					ch++;
				}
			}
			// DigiChannel 1
			if ((data[23] & (1 << 0)) != 0)
			{
				channels[16] = 1;
			}
			else
			{
				channels[16] = 0;
			}
			// DigiChannel 2
			if ((data[23] & (1 << 1)) != 0)
			{
				channels[17] = 1;
			}
			else
			{
				channels[17] = 0;
			}
			// Failsafe
			var sbusState = ReceiverStatusType.OK;
			if ((data[23] & (1 << 2)) == 1)
			{
				sbusState = ReceiverStatusType.SignalLost;
			}
			if ((data[23] & (1 << 3)) == 1)
			{
				sbusState = ReceiverStatusType.FailSafe;
			}
			ReceiverState = sbusState;

			for (int i = 0; i < 16; i++)
			{
				var origValue = channels[i];
				var percentage = ((StickCenterValue - origValue) / StickPercentStep) * -1;
				channels[i] = (int)percentage;
			}
			channels[16] = channels[16] == 0 ? -155 : 155;
			channels[17] = channels[17] == 0 ? -155 : 155;

			return channels;
		}

		/// <summary>
		/// Assigns the channel-values to the corresponsing <see cref="ItemsCollection"/>
		/// <see cref="Channel"/> instances
		/// </summary>
		protected internal bool AssignValues(int[] newChannelValues)
		{
			bool anyChanged = false;
			foreach (var channel in ItemsCollection)
			{
				var newValue = newChannelValues[channel.ChannelId - 1];
				if (channel.CurrentValue == newValue)
					continue;
				channel.CurrentValue = newValue;
				RaiseItemUpdated(channel);
				anyChanged = true;
			}
			return anyChanged;
		}

		#endregion

		#region HandleEventInvocation

		private void HandleEventInvocation(IAsyncResult result, Action<IAsyncResult> endInvoke, [CallerMemberName]string eventName = null)
		{
			try
			{
				endInvoke(result);
				//if (result.AsyncState is EventHandler)
				//	((EventHandler)result.AsyncState).EndInvoke(result);
			}
			catch (Exception ex)
			{
				var evName = eventName.StartsWith("Raise") ? eventName.Substring(5) : eventName;
				//TODO: log
				LoggingService.LogException(ex, LoggingService.ParsingErrorEventId,
					"Error on Event-Handler for event {0}.{1}. {2}",
					GetType().FullName, evName, ex.ToString());
			}
		}

		#endregion

	}
}
